<template>
	<view>
		<template v-if="showLoading">
			<view class="ly-loader ly-flex-center">
				<view class="ly-loader-inner">{{ $t('加载中') }}...</view>
			</view>
		</template>

		<template v-else>
			<view :key="updateKey" class="ly-tree" :class="{ 'is-empty': isEmpty || !visible }" role="tree"
				name="LyTreeExpand">
				<ly-tree-node v-for="nodeId in childNodesId" :nodeId="nodeId" :render-after-expand="renderAfterExpand"
					:show-checkbox="showCheckbox" :show-radio="showRadio" :check-only-leaf="checkOnlyLeaf"
					:key="getNodeKey(nodeId)" :indent="indent" :open-class="openClass" :icon-class="iconClass"
					:other-class="otherClass">
				</ly-tree-node>
			</view>
		</template>
	</view>
</template>

<script setup lang="ts" name="LyTree">
import TreeStore from './model/tree-store';
import { getNodeKey as _getNodeKey } from './tool/util';
import LyTreeNode from './ly-tree-node.vue';
import { ref, reactive, watch, computed, nextTick, onMounted, onDeactivated, provide } from 'vue';


interface IP {
	// 展示数据
	treeData: any[],

	// 自主控制loading加载，避免数据还没获取到的空档出现“暂无数据”字样
	ready?: boolean,

	// 内容为空的时候展示的文本
	emptyText?: string,

	// 是否在第一次展开某个树节点后才渲染其子节点
	renderAfterExpand?: boolean,

	// 每个树节点用来作为唯一标识的属性，整棵树应该是唯一的
	nodeKey?: string,

	// 在显示复选框的情况下，是否严格的遵循父子不互相关联的做法，默认为 false
	checkStrictly?: boolean,

	// 是否默认展开所有节点
	defaultExpandAll?: boolean,

	// 切换全部展开、全部折叠
	toggleExpendAll?: boolean,

	// 是否在点击节点的时候展开或者收缩节点， 默认值为 true，如果为 false，则只有点箭头图标的时候才会展开或者收缩节点
	expandOnClickNode?: boolean

	// 选中的时候展开节点
	expandOnCheckNode?: boolean

	// 是否在点击节点的时候选中节点，默认值为 false，即只有在点击复选框时才会选中节点
	checkOnClickNode?: boolean,
	checkDescendants?: boolean

	// 展开子节点的时候是否自动展开父节点
	autoExpandParent?: boolean

	// 默认勾选的节点的 key 的数组
	defaultCheckedKeys?: any[],

	// 默认展开的节点的 key 的数组
	defaultExpandedKeys?: any[],

	// 是否展开当前节点的父节点
	expandCurrentNodeParent?: boolean,

	// 当前选中的节点
	currentNodeKey?: Array<string | number>,

	// 是否最后一层叶子节点才显示单选/多选框
	checkOnlyLeaf?: boolean

	// 节点是否可被选择
	showCheckbox?: boolean

	// 节点单选
	showRadio?: boolean

	// 配置选项
	props?: {
		children: string, // 指定子树为节点对象的某个属性值
		label: string, // 指定节点标签为节点对象的某个属性值
		disabled: string //	指定节点选择框是否禁用为节点对象的某个属性值
	}


	// 是否懒加载子节点，需与 load 方法结合使用
	lazy?: boolean

	// 是否高亮当前选中节点，默认值是 false
	highlightCurrent?: boolean,

	// 加载子树数据的方法，仅当 lazy 属性为true 时生效
	load?: Function,

	// 对树节点进行筛选时执行的方法，返回 true 表示这个节点可以显示，返回 false 则表示这个节点会被隐藏
	filterNodeMethod?: Function,

	// 搜索时是否展示匹配项的所有子节点
	childVisibleForFilterNode?: boolean

	// 是否每次只打开一个同级树节点展开
	accordion?: boolean,

	// 相邻级节点间的水平缩进，单位为像素
	indent?: number

	// 自定义树节点的展开图标
	iconClass?: string
	openClass?: string
	otherClass?: string
	// 是否显示节点图标，如果配置为true,需要配置props中对应的图标属性名称
	showNodeIcon?: boolean

	// 当节点图标显示出错时，显示的默认图标
	defaultNodeIcon?: string

	// 如果数据量较大，建议不要在node节点中添加parent属性，会造成性能损耗
	isInjectParentInNode?: boolean
	/**
	 * 搜索的文字
	 */
	search?: string
}

const p = withDefaults(defineProps<IP>(), {
	ready: true,
	emptyText: '暂无数据',
	renderAfterExpand: true,
	expandOnClickNode: true,
	expandOnCheckNode: true,
	checkDescendants: false,
	autoExpandParent: true,
	checkOnlyLeaf: false,
	showCheckbox: false,
	showRadio: false,
	props: () => {
		return {
			children: 'children', // 指定子树为节点对象的某个属性值
			label: 'label', // 指定节点标签为节点对象的某个属性值
			disabled: 'disabled' //	指定节点选择框是否禁用为节点对象的某个属性值
		}
	},
	lazy: false,
	childVisibleForFilterNode: false,
	indent: 18,
	iconClass: '../../static/componentsImg/close.png',
	openClass: '../../static/componentsImg/open.png',
	otherClass: '../../static/componentsImg/manager.png',
	defaultNodeIcon: 'https://img-cdn-qiniu.dcloud.net.cn/uniapp/doc/github.svg',
	showNodeIcon: false,
	isInjectParentInNode: false
})

interface Ie {
	(e: 'ly-tree-render-completed'): void
	(e: 'node-click'): void
	(e: 'node-expand'): void
	(e: 'current-change'): void
	(e: 'node-collapse'): void
}

const emit = defineEmits<Ie>()

const updateKey = ref(new Date().getTime())
const elId = ref(`ly_${Math.ceil(Math.random() * 10e5).toString(36)}`)
const visible = ref(false)



const store = reactive(new TreeStore({
	key: p.nodeKey,
	data: p.treeData,
	lazy: p.lazy,
	props: p.props,
	load: p.load,
	showCheckbox: p.showCheckbox,
	showRadio: p.showRadio,
	currentNodeKey: p.currentNodeKey,
	checkStrictly: p.checkStrictly || p.checkOnlyLeaf,
	checkDescendants: p.checkDescendants,
	expandOnCheckNode: p.expandOnCheckNode,
	defaultCheckedKeys: p.defaultCheckedKeys,
	defaultExpandedKeys: p.defaultExpandedKeys,
	expandCurrentNodeParent: p.expandCurrentNodeParent,
	autoExpandParent: p.autoExpandParent,
	defaultExpandAll: p.defaultExpandAll,
	filterNodeMethod: p.filterNodeMethod,
	childVisibleForFilterNode: p.childVisibleForFilterNode,
	showNodeIcon: p.showNodeIcon,
	isInjectParentInNode: p.isInjectParentInNode
}))

const currentNode = ref()

const childNodesId = ref([])

const isEmpty = computed(() => {
	if (store.root) {
		const childNodes = store.root.getChildNodes(childNodesId.value);

		return !childNodes || childNodes.length === 0 || childNodes.every(({ visible }) => !visible);
	}

	return true;
})

const showLoading = computed(() => {
	return !(store.ready && p.ready);
})



/*
 * @description 对树节点进行筛选操作
 * @method filter
 * @param {all} value 在 filter-node-method 中作为第一个参数
 * @param {Object} data 搜索指定节点的节点数据，不传代表搜索所有节点，假如要搜索A节点下面的数据，那么nodeData代表treeData中A节点的数据
*/
function filter(value: any, data?: any) {
	if (!p.filterNodeMethod) throw new Error('[Tree] filterNodeMethod is required when filter');
	store.filter(value, data);
}

/*
 * @description 获取节点的唯一标识符
 * @method getNodeKey
 * @param {String, Number} nodeId
 * @return {String, Number} 匹配到的数据中的某一项数据
*/
function getNodeKey(nodeId: any) {
	let node = store.root.getChildNodes([nodeId])[0];
	return _getNodeKey(p.nodeKey, node.data);
}

/*
 * @description 获取节点路径
 * @method getNodePath
 * @param {Object} data 节点数据
 * @return {Array} 路径数组
*/
function getNodePath(data: any) {
	return store.getNodePath(data);
}

/*
 * @description 若节点可被选择（即 show-checkbox 为 true），则返回目前被选中的节点所组成的数组
 * @method getCheckedNodes
 * @param {Boolean} leafOnly 是否只是叶子节点，默认false
 * @param {Boolean} includeHalfChecked 是否包含半选节点，默认false
 * @return {Array} 目前被选中的节点所组成的数组
*/
function getCheckedNodes(leafOnly: boolean | undefined, includeHalfChecked: boolean | undefined) {
	return store.getCheckedNodes(leafOnly, includeHalfChecked);
}

/*
 * @description 若节点可被选择（即 show-checkbox 为 true），则返回目前被选中的节点的 key 所组成的数组
 * @method getCheckedKeys
 * @param {Boolean} leafOnly 是否只是叶子节点，默认false,若为 true 则仅返回被选中的叶子节点的 keys
 * @param {Boolean} includeHalfChecked 是否返回indeterminate为true的节点，默认false
 * @return {Array} 目前被选中的节点所组成的数组
*/
function getCheckedKeys(leafOnly: boolean | undefined, includeHalfChecked: boolean | undefined) {
	return store.getCheckedKeys(leafOnly, includeHalfChecked);
}

/*
 * @description 获取当前被选中节点的 data，若没有节点被选中则返回 null
 * @method getCurrentNode
 * @return {Object} 当前被选中节点的 data，若没有节点被选中则返回 null
*/
function getCurrentNode() {
	const currentNode = store.getCurrentNode();
	return currentNode ? currentNode.data : null;
}

/*
 * @description 获取当前被选中节点的 key，若没有节点被选中则返回 null
 * @method getCurrentKey
 * @return {all} 当前被选中节点的 key， 若没有节点被选中则返回 null
*/
function getCurrentKey() {
	const currentNode = getCurrentNode();
	if (p.nodeKey) {
		return currentNode ? currentNode[p.nodeKey] : null;
	}
	return null
}

/*
 * @description 设置全选/取消全选
 * @method setCheckAll
 * @param {Boolean} isCheckAll 选中状态,默认为true
*/
function setCheckAll(isCheckAll = true) {
	if (p.showRadio) throw new Error('You set the "show-radio" property, so you cannot select all nodes');

	if (!p.showCheckbox) console.warn('You have not set the property "show-checkbox". Please check your settings');

	store.setCheckAll(isCheckAll);
}


/*
 * @description 设置目前勾选的节点
 * @method setCheckedNodes
 * @param {Array} nodes 接收勾选节点数据的数组
 * @param {Boolean} leafOnly 是否只是叶子节点, 若为 true 则仅设置叶子节点的选中状态，默认值为 false
*/
function setCheckedNodes(nodes: any, leafOnly: boolean | undefined) {
	store.setCheckedNodes(nodes, leafOnly);
}

/*
 * @description 通过 keys 设置目前勾选的节点
 * @method setCheckedKeys
 * @param {Array} keys 勾选节点的 key 的数组 
 * @param {Boolean} leafOnly 是否只是叶子节点, 若为 true 则仅设置叶子节点的选中状态，默认值为 false
*/
function setCheckedKeys(keys: any, leafOnly: boolean | undefined) {
	if (!p.nodeKey) throw new Error('[Tree] nodeKey is required in setCheckedKeys');
	store.setCheckedKeys(keys, leafOnly);
}

/*
 * @description 通过 key / data 设置某个节点的勾选状态
 * @method setChecked
 * @param {all} data 勾选节点的 key 或者 data 
 * @param {Boolean} checked 节点是否选中
 * @param {Boolean} deep 是否设置子节点 ，默认为 false
*/
function setChecked(data: any, checked: any, deep: any) {
	store.setChecked(data, checked, deep);
}

/*
 * @description 若节点可被选择（即 show-checkbox 为 true），则返回目前半选中的节点所组成的数组
 * @method getHalfCheckedNodes
 * @return {Array} 目前半选中的节点所组成的数组
*/
function getHalfCheckedNodes() {
	return store.getHalfCheckedNodes();
}

/*
 * @description 若节点可被选择（即 show-checkbox 为 true），则返回目前半选中的节点的 key 所组成的数组
 * @method getHalfCheckedKeys
 * @return {Array} 目前半选中的节点的 key 所组成的数组
*/
function getHalfCheckedKeys() {
	return store.getHalfCheckedKeys();
}

/*
 * @description 通过 node 设置某个节点的当前选中状态
 * @method setCurrentNode
 * @param {Object} node 待被选节点的 node
*/
function setCurrentNode(node: any) {
	if (!p.nodeKey) throw new Error('[Tree] nodeKey is required in setCurrentNode');
	store.setUserCurrentNode(node);
}
/*
 * @description 通过 key 设置某个节点的当前选中状态
 * @method setCurrentKey
 * @param {all} key 待被选节点的 key，若为 null 则取消当前高亮的节点
*/
function setCurrentKey(key: any) {
	if (!p.nodeKey) throw new Error('[Tree] nodeKey is required in setCurrentKey');
	store.setCurrentNodeKey(key);
}

/*
 * @description 根据 data 或者 key 拿到 Tree 组件中的 node
 * @method getNode
 * @param {all} data 要获得 node 的 key 或者 data
*/
function getNode(data: any) {
	return store.getNode(data);
}

/*
 * @description 删除 Tree 中的一个节点
 * @method remove
 * @param {all} data 要删除的节点的 data 或者 node
*/
function remove(data: any) {
	store.remove(data);
}

/*
 * @description 为 Tree 中的一个节点追加一个子节点
 * @method append
 * @param {Object} data 要追加的子节点的 data 
 * @param {Object} parentNode 子节点的 parent 的 data、key 或者 node
*/
function append(data: any, parentNode: any) {
	store.append(data, parentNode);
}

/*
 * @description 为 Tree 的一个节点的前面增加一个节点
 * @method insertBefore
 * @param {Object} data 要增加的节点的 data 
 * @param {all} refNode 要增加的节点的后一个节点的 data、key 或者 node
*/
function insertBefore(data: any, refNode: any) {
	store.insertBefore(data, refNode);
}

/*
 * @description 为 Tree 的一个节点的后面增加一个节点
 * @method insertAfter
 * @param {Object} data 要增加的节点的 data 
 * @param {all} refNode 要增加的节点的前一个节点的 data、key 或者 node
*/
function insertAfter(data: any, refNode: any) {
	store.insertAfter(data, refNode);
}

/*
 * @description 通过 keys 设置节点子元素
 * @method updateKeyChildren
 * @param {String, Number} key 节点 key 
 * @param {Object} data 节点数据的数组
*/
function updateKeyChildren(key: any, data: any) {
	if (!p.nodeKey) throw new Error('[Tree] nodeKey is required in updateKeyChild');
	store.updateChildren(key, data);
}

const isTree = ref(false)

onMounted(() => {
	isTree.value = true;
	childNodesId.value = store.root.childNodesId;

})

onDeactivated(() => {
	if (p.accordion) {
		uni.$off(`${elId.value}-tree-node-expand`)
	}
})


watch(() => p.toggleExpendAll, newVal => {
	store.toggleExpendAll(newVal)
})

watch(() => p.defaultCheckedKeys, newVal => {
	console.log(newVal)
	store.setDefaultCheckedKey(newVal)
})

watch(() => p.defaultExpandedKeys, newVal => {
	store.defaultExpandedKeys = newVal
	store.setDefaultExpandedKeys(newVal)
})

watch(() => p.checkStrictly, newVal => {
	store.checkStrictly = newVal || p.checkOnlyLeaf;
})

watch(() => store.root?.childNodesId, newVal => {
	childNodesId.value = newVal
})

watch(() => store.root?.visible, newVal => {
	visible.value = newVal
})

watch(() => childNodesId.value, newVal => {
	nextTick(() => {
		emit('ly-tree-render-completed');
	})
})

watch(() => p.treeData, newVal => {
	updateKey.value = new Date().getTime();
	store.setData(newVal);
})

watch(() => p.search, newVal => {
	filter(newVal)
})


provide('tree', {
	store,
	nodeKey: p.nodeKey,
	expandOnCheckNode: p.expandOnCheckNode,
	checkStrictly: p.checkStrictly,
	elId: elId.value,
	accordion: p.accordion,
	defaultNodeIcon: p.defaultNodeIcon,
	$emit: emit,
	highlightCurrent: p.highlightCurrent,
	props: p.props,
	checkOnClickNode: p.checkOnClickNode,
	currentNode: currentNode.value,
	expandOnClickNode: p.expandOnClickNode,
})





</script>

<style>
.ly-tree {
	position: relative;
	cursor: default;
	background: #FFF;
	color: #606266;
	/* padding: 30rpx; */
}

.ly-tree.is-empty {
	background: transparent;
}

/* lyEmpty-start */
.ly-empty {
	width: 100%;
	display: flex;
	justify-content: center;
	margin-top: 100rpx;
}

/* lyEmpty-end */

/* lyLoader-start */
.ly-loader {
	margin-top: 100rpx;
	display: flex;
	align-items: center;
	justify-content: center;
}

.ly-loader-inner,
.ly-loader-inner:before,
.ly-loader-inner:after {
	background: #efefef;
	animation: load 1s infinite ease-in-out;
	width: .5em;
	height: 1em;
}

.ly-loader-inner:before,
.ly-loader-inner:after {
	position: absolute;
	top: 0;
	content: '';
}

.ly-loader-inner:before {
	left: -1em;
}

.ly-loader-inner {
	text-indent: -9999em;
	position: relative;
	font-size: 22rpx;
	animation-delay: 0.16s;
}

.ly-loader-inner:after {
	left: 1em;
	animation-delay: 0.32s;
}

/* lyLoader-end */

@keyframes load {

	0%,
	80%,
	100% {
		box-shadow: 0 0 #efefef;
		height: 1em;
	}

	40% {
		box-shadow: 0 -1.5em #efefef;
		height: 1.5em;
	}
}
</style>
